﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Windows.Forms;
using Microsoft.HomeServer.Controls;
using Umbrella.Net.Contracts;
using Umbrella.Whs.SharePoint;
using Umbrella.Whs.WhsAddin.Controls;
using Xceed.Zip;

namespace Umbrella.Whs.WhsAddin.Wizards.SharePointInstallation
{
    internal partial class Step30_Download : WizardPageClientBannerTop
    {
        private readonly log4net.ILog _log = log4net.LogManager.GetLogger(typeof(Step30_Download));
        private InstallationPackagesManager _instPackMgr = new InstallationPackagesManager();
        private WebClient _webClient = new WebClient();
        private Queue<SharePointInstallationFile> _toDownload;
        private Queue<SharePointInstallationFile> _toExtract;

        private SharePointInstallationWizard ParentWizard2
        {
            get { return (SharePointInstallationWizard)this.ParentWizard; }
        }

        public Step30_Download()
        {
            InitializeComponent();

            // register events
            _webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressChanged);
            _webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCompleted);
        }

        private void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var ip = (SharePointInstallationFile)e.UserState;

            // failed?
            if (e.Error != null)
            {
                _log.Error("Failed to download file: " + ip.ZipDownloadUrl, e.Error);
            }

            // abort download?
            if (e.Cancelled || (e.Error != null))
            {
                this.downloadFilledBar.Value1 = 0;
                this.downloadFilledBar.Text = GetProgressTextValue(0);
                this.downloadStatusLabel.Text = Resources.txtFailed;

                CancelAllOperations();

                return;
            }

            // done. start extracting
            _log.Info("Completed downloading file: " + ip.ZipDownloadUrl);

            _toExtract.Enqueue(ip);

            TriggerNextExtraction();

            // trigger next download, if everything is fine
            if (!TriggerNextDownload())
            {
                this.downloadFilledBar.Value1 = 0;
                this.downloadFilledBar.Text = GetProgressTextValue(0);
                this.downloadStatusLabel.Text = Resources.txtDownloadIdle;
            }
        }

        private void CancelAllOperations()
        {
            _log.Info("Canceling all operations...");

            // there is no going back now
            this.completionObserverTimer.Stop();

            // cancel everything
            if (this.extractBgWorker.IsBusy)
                this.extractBgWorker.CancelAsync();

            if (_webClient.IsBusy)
                _webClient.CancelAsync();

            // wait until everything is down
            this.cancelObserverTimer.Start();
        }

        private bool TriggerNextDownload()
        {
            if (_toDownload.Count <= 0)
                return false;

            // trigger next download
            var ip = _toDownload.Dequeue();

            _log.Info("Start downloading file: " + ip.ZipDownloadUrl);

            // update status text
            try
            {
                var totalFiles = this.ParentWizard2.SelectedInstallationFiles.Count;

                this.downloadFilledBar.Value1 = 0;
                this.downloadFilledBar.Text = GetProgressTextValue(0);
                this.downloadStatusLabel.Text = string.Format(
                    Resources.txtDownloadingFile,
                    this.ParentWizard2.GetTitleForInstallationPackage(ip),
                    totalFiles - _toDownload.Count,
                    totalFiles,
                    "? KB"
                    );

                // start download
                _webClient.DownloadFileAsync(
                    new Uri(ip.ZipDownloadUrl),
                    Path.Combine(this.ParentWizard2.InstallationTempDir.FullName, ip.GetZipFilename()),
                    ip
                    );

                return true;
            }
            catch (Exception x)
            {
                _log.Error("Failed to trigger next download: " + ip.ZipDownloadUrl, x);

                throw;
            }
        }

        private bool TriggerNextExtraction()
        {
            if ((_toExtract.Count <= 0) || this.extractBgWorker.IsBusy)
                return false;

            // trigger next extraction
            var ip = _toExtract.Dequeue();

            _log.Info("Start extracting file: " + ip.ZipDownloadUrl);

            this.extractFilledBar.Value1 = 0;
            this.extractFilledBar.Text = GetProgressTextValue(0);
            this.extractStatusLabel.Text = string.Format(
                Resources.txtExtractionProgress,
                this.ParentWizard2.GetTitleForInstallationPackage(ip)
                );

            // start extracting
            this.extractBgWorker.RunWorkerAsync(ip);

            return true;
        }

        private void Step30_Download_CancelButtonClick(object sender, EventArgs e)
        {
            if (QMessageBox.Show(
                Resources.txtCancelVerifyText,
                Resources.txtCancelVerifyTitle,
                MessageBoxButtons.YesNo,
                MessageBoxIcon.Question
                ) != DialogResult.Yes)
            {
                return;
            }

            this.CancelButtonEnabled = false;

            CancelAllOperations();
        }

        private void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            var ip = (SharePointInstallationFile)e.UserState;

            // update progress bar
            this.downloadFilledBar.Value1 = e.ProgressPercentage;
            this.downloadFilledBar.Text = GetProgressTextValue(e.ProgressPercentage);

            // update status text
            var totalFiles = this.ParentWizard2.SelectedInstallationFiles.Count;

            this.downloadStatusLabel.Text = string.Format(
                Resources.txtDownloadingFile,
                this.ParentWizard2.GetTitleForInstallationPackage(ip),
                totalFiles - _toDownload.Count,
                totalFiles,
                Whs.UmbrellaHelpers.GetNiceFileSize(e.TotalBytesToReceive)
                );
        }

        public override void StartRealWork()
        {
            base.StartRealWork();

            try
            {
                // prepare downloading
                // install core first because it's the largest file
                // to maximize concurrency
                var optimized = (
                    from ip in this.ParentWizard2.SelectedInstallationFiles
                    orderby (ip.LanguageLCID == InstallationManager.RootLanguageLCID) descending
                    select ip
                    );

                _toDownload = new Queue<SharePointInstallationFile>(optimized);
                _toExtract = new Queue<SharePointInstallationFile>();

                TriggerNextDownload();

                // wait for being finished
                this.completionObserverTimer.Start();
            }
            catch (Exception x)
            {
                _log.Error("Failed to start download and extraction", x);

                throw;
            }
        }

        private void Step30_Download_Load(object sender, EventArgs e)
        {
            this.downloadFilledBar.Value1 = 0;
            this.downloadFilledBar.Text = GetProgressTextValue(0);
            this.downloadStatusLabel.Text = Resources.txtPreparing;

            this.extractFilledBar.Value1 = 0;
            this.extractFilledBar.Text = GetProgressTextValue(0);
            this.extractStatusLabel.Text = Resources.txtExtractionIdle;
        }

        private void ExtractByteProgression(string currentItemName, long currentItemBytesProcessed, long currentItemTotalBytes, byte currentItemPercent, long allFilesBytesProcessed, long allFilesTotalBytes, byte allFilesPercent, object userParams)
        {
            this.extractBgWorker.ReportProgress(allFilesPercent, userParams);
        }

        private void extractBgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            // copy context
            e.Result = e.Argument;

            // continue
            var ip = (SharePointInstallationFile)e.Argument;
            var zipFile = new FileInfo(Path.Combine(this.ParentWizard2.InstallationTempDir.FullName, ip.GetZipFilename()));

            try
            {
	            if (!zipFile.Exists)
	                throw new Exception("ZIP file does not exist: " + zipFile.FullName);

	            // create directory
                var xDir = _instPackMgr.GetExtractionDirectory(ip);

                if (!xDir.Exists)
                    xDir.Create();

                // extract
	            QuickZip.Unzip(
	                zipFile.FullName,
	                xDir.FullName,
	                ip.ZipPassword,
	                true,
	                true,
	                true,
	                null,
                    new QuickZip.ByteProgressionCallback(ExtractByteProgression),
	                null,
	                ip,
                    "*.*"
	                );

                // delete zip file
                zipFile.Delete();
            }
            catch (Exception ex)
            {
                _log.Error("Failed to unzip installation files: " + zipFile.FullName, ex);

                throw;
            }
        }

        private void extractBgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //var ip = (SharePointInstallationFile)e.UserState;
            
            this.extractFilledBar.Value1 = e.ProgressPercentage;
            this.extractFilledBar.Text = GetProgressTextValue(e.ProgressPercentage);
        }

        private void extractBgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // extraction failed?
            if (e.Error != null)
            {
                _log.Error("Failed to extract file", e.Error);

                this.extractFilledBar.Value1 = 0;
                this.extractFilledBar.Text = GetProgressTextValue(0);
                this.extractStatusLabel.Text = Resources.txtFailed;

                QMessageBox.Show(
                    e.Error.Message,
                    Resources.txtInstallFailedTitle,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error
                    );
            }

            // cancel?
            if ((e.Error != null) || e.Cancelled)
            {
                CancelAllOperations();

                QMessageBox.Show(
                    Resources.txtInstallAbortedText,
                    Resources.txtInstallAbortedTitle,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning
                    );

                return;
            }

            // everything went fine
            var ip = (SharePointInstallationFile)e.Result;
            
            _log.Info("Completed extracting file: " + ip.ZipDownloadUrl);

            this.extractFilledBar.Value1 = 0;
            this.extractFilledBar.Text = GetProgressTextValue(0);
            this.extractStatusLabel.Text = Resources.txtExtractionIdle;

            TriggerNextExtraction();
        }

        private void cancelObserverTimer_Tick(object sender, EventArgs e)
        {
            if (_webClient.IsBusy || this.extractBgWorker.IsBusy)
                return;

            this.cancelObserverTimer.Stop();

            // trigger cleanup
            this.downloadStatusLabel.Text = Resources.txtAbortedCleaningUp;
            this.extractStatusLabel.Text = Resources.txtAbortedCleaningUp;

            // since this is an error, close the wizard
            this.ParentWizard.Close();
        }

        private void completionObserverTimer_Tick(object sender, EventArgs e)
        {
            if (_webClient.IsBusy ||
                this.extractBgWorker.IsBusy ||
                (_toDownload.Count > 0) ||
                (_toExtract.Count > 0))
            {
                return;
            }

            // we're done here
            this.completionObserverTimer.Stop();

            SetCurrentPage<Step40_Install>();
        }

        private static string GetProgressTextValue(int v)
        {
            return string.Format("{0}%", v);
        }
    }
}
